#define _CRT_SECURE_NO_WARNINGS

#include "logger.h"

#ifdef WIN32
#include <process.h>
#define THREAD_LOCAL __declspec(thread)
#define DIR_SEPERATOR "\\"
#else
#include <sys/types.h>
#include <unistd.h>
#define THREAD_LOCAL  __thread
#define DIR_SEPERATOR "/"
#endif // DEBUG

#include <stdio.h>
#include <algorithm>
#include <sstream>
#include <iomanip>

#include <boost/filesystem.hpp>

#include "utils.hpp"
#include "timestamp.h"

#define FORMATE_MSG_USE_STD_STRING 0
#define ROLL_SIZE 100 * 1000 * 1000

namespace
{
    const char* LogLevelCStr[] = { "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NUM_LOG_LEVELS" };
    int LogLevelCStrLen[] = { 5, 5, 4, 4, 5, 5 };

    THREAD_LOCAL  char local_current_thread_id[32] = {0};
    THREAD_LOCAL  int local_current_thread_len = 0;
    inline const char* currentThreadIdCStr() 
    {
        if (local_current_thread_len == 0)
        {
            /*
            std::ostringstream out;
            out << std::setw(6) << std::setfill('0') << std::setiosflags(std::ios::right | std::ios::hex) << std::this_thread::get_id();
            std::string tmp = out.str();
            memcpy(local_current_thread_id, tmp.c_str(), tmp.size());
            local_current_thread_len = tmp.size();
            */

            local_current_thread_len = sprintf(local_current_thread_id, "%0x", std::this_thread::get_id());
        }
        return local_current_thread_id;
    }
}

namespace easyasio {
namespace base {

class LogFile : easyasio::base::noncopyable
{
public:
    LogFile(const std::string& fullbasename)
        :written_bytes_(0) 
    { 
        std::string name = fullbasename;
        name.append(".");
        std::string timestr = easyasio::base::Timestamp::now().toFormattedStringCorrectToSecs();
        name.append(timestr);
        name.append(".");
        std::ostringstream out;
        out << std::setw(6) << std::setfill('0') << std::setiosflags(std::ios::right) << (int)getpid();
        name.append(out.str());
        name.append(".log");
        
        file_ = fopen(name.c_str(), "wb");
    }

    ~LogFile() { fclose(file_); }

    void append(const char* msg, int len) { written_bytes_ += fwrite(msg, 1, len, file_); }
    size_t writtenBytes() const { return written_bytes_; }
    void fflush() { ::fflush(file_); }

private:
    FILE* file_;
    size_t written_bytes_;
};

std::string Logger::formateMsg(const std::string& s, LogLevel level)
{
#if FORMATE_MSG_USE_STD_STRING   
    std::string formatstr;
    formatstr.append(Timestamp::now().toFormattedStringCorrectToMicrosecs());
    formatstr.append(1, ' ');
    formatstr.append(currentThreadIdCStr(), local_current_thread_len);
    formatstr.append(1, ' ');
    formatstr.append(LogLevelCStr[level], LogLevelCStrLen[level]);
    formatstr.append(1, ' ');
    formatstr.append(s);
    formatstr.append(1, '\n');
    return std::move(formatstr);

#else
#define FORMAT_MSG_LEN 512
    char format_msg[FORMAT_MSG_LEN + 2];
    int format_msg_len = 0;

    //time 
    std::string timestr = Timestamp::now().toFormattedStringCorrectToMicrosecs();
    memcpy(format_msg+format_msg_len , timestr.c_str(), timestr.size());
    format_msg_len += timestr.size();
    format_msg[format_msg_len ++] = ' ';

    //time threadid
    if (local_current_thread_len == 0)
        currentThreadIdCStr();
    memcpy(format_msg+format_msg_len , currentThreadIdCStr(), local_current_thread_len);
    format_msg_len  += local_current_thread_len;
    format_msg[format_msg_len ++] = ' ';

    //time threadid level
    memcpy(format_msg+format_msg_len , LogLevelCStr[level], LogLevelCStrLen[level]);
    format_msg_len += LogLevelCStrLen[level];
    format_msg[format_msg_len ++] = ' ';

    //time threadid level msg
    int writelen = std::min(s.size(), (size_t)(FORMAT_MSG_LEN - format_msg_len));
    memcpy(format_msg+format_msg_len, s.c_str(), writelen);
    format_msg_len += writelen;
    format_msg[format_msg_len++] = '\n';
    format_msg[format_msg_len] = '\0';

    return format_msg;
#endif
}

void Logger::output(const std::string& s, LogLevel level)
{
    std::string msg = Logger::formateMsg(s, level);
    {
        std::unique_lock<std::mutex> lock(msgs_mutex_);
        msgs_.push_back(std::move(msg));
    }
    msgs_condition_variable_.notify_one();
}

Logger& Logger::instance()
{
    static Logger log;
    return log;
}

void Logger::writeMsgLoop()
{
    while (runing_)
    {
        std::list<std::string> msgs;
        {
            std::unique_lock<std::mutex> lock(msgs_mutex_);
            while (msgs_.empty() && runing_)
                msgs_condition_variable_.wait(lock);
            msgs.swap(msgs_);
        }

        std::for_each(msgs.begin(), msgs.end(), 
            [this](const std::string& msg) 
            { 
                if (logfile_)
                {
                    logfile_->append(msg.c_str(), msg.size());
                    if (logfile_->writtenBytes() > ROLL_SIZE)
                        logfile_.reset(new LogFile(fullbasepath_));
                }
                else
                    fwrite(msg.c_str(), 1, msg.size(), stdout); 
            });


        if (logfile_)
            logfile_->fflush();
        else
            fflush(stdout);
    }
}

Logger::Logger() 
    :level_(LOGLEVEL_TRACE)
{ 
    start();
}

Logger::~Logger()
{
    stop();
}

void Logger::start()
{
    runing_ = true;
    msg_writer_.reset(new std::thread(std::bind(&Logger::writeMsgLoop, this)));
}

void Logger::stop()
{
    {
        std::unique_lock<std::mutex> lock(msgs_mutex_);
        runing_ = false;
        msgs_condition_variable_.notify_one();
    }
    if (msg_writer_->joinable())
        msg_writer_->join();
}

void Logger::setBaseName(const std::string& path)
{
    mnb::filesystem::path filepath = path;
    if (!mnb::filesystem::exists(filepath.parent_path()))
        mnb::filesystem::create_directories(filepath.parent_path());

    fullbasepath_ = path;
    logfile_.reset(new LogFile(path));
}

const char* getBaseName(const char* filename)
{
    std::string strfilename(filename);
    size_t pos = strfilename.rfind(DIR_SEPERATOR);
    size_t begin = (pos == std::string::npos ? 0 : pos+1);
    return filename + begin;
}

} 
}
